package com.sardine.base.mybatis.router;

import com.sardine.base.exception.SardineException;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.util.Map;
import java.util.Properties;

/**
 * *****************************************************************************
 * <p/>
 * 分表的主要思想就是将一个表中的数据，按某种业务逻辑。尽量使一次业务请求在一个表里面实现。
 * <p/>
 * 所以应该在statement 阶段将sql语句修改后再发送给MyBatis核心执行接口中
 * <p/>
 * *****************************************************************************
 * 北京京东世纪贸易有限公司<br>
 * Created with IntelliJ IDEA.
 *
 * Date:2015/11/14
 * Time:16:43
 * MyBatis 分表拦截器
 *
 * @author daifeng<br>
 *         daifeng@jd.com
 * @since JDK 1.6 <br>
 * Copyright (c) 2015 JD System Incorporated All Rights Reserved.
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class TableIndexInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(TableIndexInterceptor.class);

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_OBJECT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
    private RouterXmlFactoryBean routerXmlFactoryBean;

    private static final String REFIX_BEGIN = "\\@\\{"; // 需要拦截的ID(正则匹配)
    private static final String REFIX_END = "\\}"; // 需要拦截的ID(正则匹配)

    private String routerXmlPath = ""; // 路由配置文件路径

    /**
     * 是实现拦截逻辑的地方，内部要通过invocation.proceed()显式地推进责任链前进，也就是调用下一个拦截器拦截目标方法。
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_OBJECT_REFLECTOR_FACTORY);
        // 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环可以分离出最原始的的目标类)
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_OBJECT_REFLECTOR_FACTORY);
        }
        // 分离最后一个代理对象的目标类
        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_OBJECT_REFLECTOR_FACTORY);
        }
        if(routerXmlFactoryBean==null){
            //配置拦截器全局的其他属性
            interceptorGlobalConfig(metaStatementHandler);
        }
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        //查询分表规则
        String sqlId = mappedStatement.getId();
        RouterXmlFactoryBean.Router router = routerXmlFactoryBean.getRouter(sqlId);
        if (router == null) {
            String namespace = sqlId.substring(0, sqlId.lastIndexOf('.'));
            router = routerXmlFactoryBean.getRouter(namespace);
        }
        // 如果存在分表规则，则利用分表规则分表执行SQL
        if (router != null) {
            BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
            Object parameterObject = boundSql.getParameterObject();
            if (parameterObject == null) {
                throw new NullPointerException("parameterObject is null!");
            } else {
                //分页对象
                Object value = metaStatementHandler
                        .getValue("delegate.boundSql.parameterObject");
                String expression = null;
                if (value instanceof Map) {
                    Map map = (Map) value;
                    for (Object o : map.values()) {
                        expression = router.getClazz().expression(o);
                        break;
                    }
                } else {
                    expression = router.getClazz().expression(value);
                }
                String sql = boundSql.getSql();
                // 重写sql,生成分表语句
                String routerSql = buildRouterSql(sql, expression, router);
                //根据句柄设置statement元数据分表，
                metaStatementHandler.setValue("delegate.boundSql.sql", routerSql);
            }
        }
        // 将执行权交给下一个拦截器
        return invocation.proceed();
    }

    /**
     * 就是用当前这个拦截器生成对目标target的代理，实际是通过Plugin.wrap(target,this) 来完成的，把目标target和拦截器this传给了包装函数。
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    /**
     * 用于设置额外的参数，参数配置在拦截器的Properties节点里。
     * <p/>
     * 注解里描述的是指定拦截方法的签名  [type,method,args] （即对哪种对象的哪种方法进行拦截），它在拦截前用于决断。
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        //获取分表路由配置
        this.routerXmlPath = properties.getProperty("routerXmlPath");
    }

    private String buildRouterSql(String sql, String expression, RouterXmlFactoryBean.Router router) {
        if (expression != null) {
            String regex = new StringBuilder(REFIX_BEGIN).append(router.getPlaceholderChar()).append(REFIX_END).toString();
            return sql.replaceAll(regex, expression);
        } else {
            logger.warn("分表正则替换字符串为null，分表执行失败。{}", sql);
        }
        if(logger.isDebugEnabled()){
            logger.debug("sql = {}",sql);
        }
        return sql;
    }

    /**
     * 从mybatis文件中读取全局属性，如果属性
     *
     * @param metaStatementHandler
     */
    private void interceptorGlobalConfig(MetaObject metaStatementHandler) {
        Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");
        //分页查询ID结束标示
        if (null == this.routerXmlPath || "".equals(this.routerXmlPath)) {
            this.routerXmlPath = configuration.getVariables().getProperty("routerXmlPath");
        }
        if(this.routerXmlPath==null || "".equals(this.routerXmlPath)){
            throw new SardineException("分表异常,没有找到可用的 router xml 配置。请在 mybatis 配置文件属性 properties 节点下，或者plugins 拦截器下配置相关路由规则。");
        }
        //解析分表路由
        this.routerXmlFactoryBean = RouterXmlFactoryBean.getInstance(routerXmlPath);
    }
}
